/*******************************************************************************
 *          st-graphic
 * file name    :   st-graphic.c
 * author       :   Moose W. Oler (wangjun.cumtb@ieee.org)   
 * description  :   
 * history      :
 *      2011-07-29  MOD rewrite st_graphic_series
 *      2011-07-14  ADD vim fold marker 
 *      2011-06-18  MOD simplify st_graphic_CreateNull() 
 *                      simplify st_graphic_Create()
 *                  ADD st_graphic_manager
 *      2011-06-xx  NEW
 ******************************************************************************/
#include <SDL.h>

#include "st-common.h"
#include "st-graphic.h"


/*******************************************************************************
 *         主表面相关{{{1
 ******************************************************************************/
st_graphic_screen   st_graphic_gScreen;         // read-only outside st-graphic

const st_uint32     ST_GRAPHIC_SCREENWIDTH = 640;
const st_uint32     ST_GRAPHIC_SCREENHEIGHT= 480;
const st_uint32     ST_GRAPHIC_COLORDEPTH  = 24;

void     _st_graphic_FreeScreen(void);
void     _st_graphic_UpdateScreen(st_graphic** graphics, st_uint32 num);
st_state _st_graphic_CopyScreen(SDL_Surface** surface);

/*******************************************************************************
 * func name    :   st_graphic_InitScreen
 * para         :
 *
 * return       :   NULL if failed
 *                  gScreen's address if successed
 ******************************************************************************/
st_graphic_screen* st_graphic_InitScreen(st_uint32 width, st_uint32 height, st_uint32 bpp)
{
    st_uint32 video_flag;

    st_graphic_gScreen.surface = NULL;
    st_graphic_gScreen.Update = _st_graphic_UpdateScreen;
    st_graphic_gScreen.Free   = _st_graphic_FreeScreen;
    st_graphic_gScreen.Copy   = _st_graphic_CopyScreen;

    if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0)
    {
		fprintf(stderr, "Couldn't initialize SDL: %s\n",SDL_GetError());
		return NULL;
    }

    video_flag = SDL_SWSURFACE;

    if (width == 0)
    {
        width = ST_GRAPHIC_SCREENWIDTH; 
    }
    if (height == 0)
    {
        height = ST_GRAPHIC_SCREENHEIGHT; 
    }
    if (bpp == 0)
    {
        bpp = ST_GRAPHIC_COLORDEPTH; 
    }

    st_graphic_gScreen.surface = SDL_SetVideoMode( width, 
                               height, 
                               bpp, 
                               video_flag);

    if ( st_graphic_gScreen.surface == NULL)
    {
        fprintf(stderr, "Error setting %dx%d indexed mode: %s\n",
                width, 
                height, 
                SDL_GetError());
        return NULL;
    }

    return &st_graphic_gScreen;
}

/*******************************************************************************
 * func name    :   st_graphic_FreeScreen
 * description  :
 *      DO NOT call this function unless you want to terminate your application
 *      and all other finalization have been done.
 ******************************************************************************/
void _st_graphic_FreeScreen(void)
{
    if (st_graphic_gScreen.surface != NULL) 
    {
        SDL_FreeSurface( st_graphic_gScreen.surface);
        st_graphic_gScreen.surface = NULL;
    }

    SDL_Quit();
}

/*******************************************************************************
 * func name    :   st_graphic_UpdateScreen
 * para         :
 *  [in]graphics    an array of pointer to st_graphic, all of which will be painted on the screen(st_graphic_gScreen)
 *  [in]num         number of pointers
 ******************************************************************************/
void _st_graphic_UpdateScreen(st_graphic** graphics, st_uint32 num)
{
    st_uint32 i;

    if ((graphics != NULL) && (num>0))
    {
        // clear screen
        SDL_FillRect(st_graphic_gScreen.surface, 
                NULL, 
                SDL_MapRGB((st_graphic_gScreen.surface)->format, 0x00, 0x00, 0x00));

        // draw graphics' fifo
        for (i = 0; i < num; i++) 
        {
            if (graphics[i] != NULL)
            {
                graphics[i]->Draw(graphics[i]);
            }
        }

        // update main surface
        SDL_UpdateRect(st_graphic_gScreen.surface, 0, 0, 0, 0);
    }
}

/*******************************************************************************
 * func name    :   _st_graphic_CopyScreen(SDL_Surface** screen)
 * para         :
 *
 * return       : 
 *      ST_OK when success
 *      ST_ERROR when the main surface is not available
 ******************************************************************************/
st_state _st_graphic_CopyScreen(SDL_Surface** surface)
{
    if (st_graphic_gScreen.surface != NULL)
    {
        *surface = SDL_ConvertSurface((st_graphic_gScreen.surface), 
                (st_graphic_gScreen.surface)->format, 
                (st_graphic_gScreen.surface)->flags); 
        return ST_OK;
    }
    else
    {
        return ST_ERROR;
    }
}
/*******************************************************************************
 *         主表面相关 END}}}1
 ******************************************************************************/

/*******************************************************************************
 *          单个图片相关{{{1
 ******************************************************************************/
st_state _st_graphic_LoadImageFromBitmap(st_graphic* self, const st_uint8* filename);
st_state _st_graphic_Assign(st_graphic* self, const st_graphic* src);
void     _st_graphic_Free(st_graphic* self);

void     _st_graphic_SetPosition(st_graphic* self, const st_vertex_3d* pos);
void     _st_graphic_GetPosition(st_graphic* self, st_vertex_3d* pos);
void     _st_graphic_SetOffset(st_graphic* self, const st_vertex_3d* offset);
void     _st_graphic_GetOffset(st_graphic* self, st_vertex_3d* offset);

void     _st_graphic_Draw(st_graphic* self);
/*******************************************************************************
 * func name    :   st_graphic_CreateGraph
 * para         :
 *  [in]owner           RESERVED
 *  [in]on_drawing      callback when drawing the graph, maybe we can do some gfx in it.
 *  [in]pos             initial position of the graph, see NOTE1
 *  [in]offset          initial offset, see NOTE1
 *
 * return       : 
 *  NULL when failed on tring to allocate resources.
 *  a st_graphic ptr when success
 ******************************************************************************/
st_graphic* st_graphic_CreateGraph(const void* owner,
                                   void  (*on_drawing)(void* arg),
                                   const st_vertex_3d* pos,
                                   const st_vertex_3d* offset)
{
    st_graphic* stg;

    stg = (st_graphic*)st_Allocate(sizeof(st_graphic));

    if (st_Assigned((void*)stg))
    {
        stg->owner = (void*)owner;
        stg->image = NULL;
        stg->on_drawing = on_drawing;

        stg->LoadImageFromFile= _st_graphic_LoadImageFromBitmap;
        stg->Draw             = _st_graphic_Draw;
        stg->SetPos           = _st_graphic_SetPosition;
        stg->GetPos           = _st_graphic_GetPosition;
        stg->SetOffset        = _st_graphic_SetOffset;
        stg->GetOffset        = _st_graphic_GetOffset;

        stg->SetPos( stg, pos);
        stg->SetOffset( stg, offset);

        stg->Assign   = _st_graphic_Assign;
        stg->Free     = _st_graphic_Free;
        
        return stg;
    }
    else
    {
        return NULL;
    }
}

/*******************************************************************************
 * func name    :
 * para         :
 *
 * return       : 
 ******************************************************************************/
st_state _st_graphic_LoadImageFromBitmap(st_graphic* self, const st_uint8* filename)
{
    if (self->image != NULL)
    {

        SDL_FreeSurface(self->image);
    }

    self->image = SDL_LoadBMP(filename);
    if ( self->image == NULL)
    {
        return ST_ERROR;
    }

    SDL_SetColorKey(self->image, 
                    SDL_SRCCOLORKEY ,
                    SDL_MapRGB(self->image->format, 
                               ST_GRAPHIC_COLORKEY_R, 
                               ST_GRAPHIC_COLORKEY_G, 
                               ST_GRAPHIC_COLORKEY_B)
                    );

    return ST_OK;
}

/*******************************************************************************
 * func name    :   st_graphic_Assign
 * para         :
 *
 * return       : 
 ******************************************************************************/
st_state _st_graphic_Assign(st_graphic* self, const st_graphic* src)
{
    if ((st_Assigned((void*)self)) && 
        (st_Assigned((void*)src)) && 
        (src->image != NULL))
    {
        self->image = src->image;
        (src->image->refcount)++;
        return ST_OK;
    }
    else
    {
        return ST_ERROR;
    }
}

/*******************************************************************************
 * func name    :
 * para         :
 *
 * return       : 
 ******************************************************************************/
void _st_graphic_Free(st_graphic* self)
{
    if (st_Assigned((void*)self)) 
    {
        if ((self->image != NULL))
        {
            if ((self->image->refcount) == 0)
            {
                SDL_FreeSurface(self->image);
            }
            else
            {
                (self->image->refcount)-=1;
            }
        }

        st_Free(self);
    }
}

/*******************************************************************************
 * func name    :
 * para         :
 *
 * return       : 
 ******************************************************************************/
void _st_graphic_Draw(st_graphic* self)
{
    SDL_Rect rect;

    if (self->on_drawing != NULL)
    {
        self->on_drawing(self->owner);
    }

    rect.x = self->pos.x + self->offset.x;
    rect.y = self->pos.y + self->offset.y;
    rect.w = self->image->w;
    rect.h = self->image->h;

    SDL_BlitSurface(self->image, NULL, st_graphic_gScreen.surface, &rect);
}


void     _st_graphic_SetPosition(st_graphic* self, const st_vertex_3d* pos)
{
    if (st_Assigned(self)) 
    {
        self->pos.x = pos->x;
        self->pos.y = pos->y;
        self->pos.z = pos->z;
    }
}

void     _st_graphic_GetPosition(st_graphic* self, st_vertex_3d* pos)
{
    if (st_Assigned(self)) 
    {
        pos->x = self->pos.x;
        pos->y = self->pos.y;
        pos->z = self->pos.z;
    }
}

void     _st_graphic_SetOffset(st_graphic* self, const st_vertex_3d* offset)
{
    if (st_Assigned(self)) 
    {
        self->offset.x = offset->x;
        self->offset.y = offset->y;
        self->offset.z = offset->z;
    }
}
void     _st_graphic_GetOffset(st_graphic* self, st_vertex_3d* offset)
{
    if (st_Assigned(self)) 
    {
        offset->x = self->offset.x;
        offset->y = self->offset.y;
        offset->z = self->offset.z;
    }
}
/*******************************************************************************
 *          单个图片相关 END}}}1
 ******************************************************************************/

/*******************************************************************************
 *          多个图片(动画)相关{{{1
 ******************************************************************************/
/*******************************************************************************
 *      st_graphic_series
 * it can keep a series of st_graphic. All graphics in a series has some common
 * properties, such as pos, owner. it is very useful to compose an animation.
 * HOW TO use it:
 *
 ******************************************************************************/
void        _st_graphic_series_SetPos(st_graphic_series* self, const st_vertex_3d* pos);
st_state    _st_graphic_series_Add(st_graphic_series* self, st_graphic* graphic);
void        _st_graphic_series_Clear(st_graphic_series* self);
//st_state    _st_graphic_series_Chain(st_graphic_series* self, st_graphic_series next);
st_graphic* _st_graphic_series_Update(st_graphic_series* self);
st_boolean  _st_graphic_series_Free(st_graphic_series* self);

/*******************************************************************************
 * func name    :
 * para         :
 *
 * return       : 
 ******************************************************************************/
st_graphic_series*    st_graphic_series_Create(const st_vertex_3d* pos, const st_uint32 dt)
{
    st_graphic_series* stgs;

    stgs = (st_graphic_series*)st_Allocate(sizeof(st_graphic_series));

    if (st_Assigned((void*)stgs))
    {
        stgs->SetPos    = _st_graphic_series_SetPos;
        stgs->Add       = _st_graphic_series_Add;
        stgs->Clear     = _st_graphic_series_Clear;
        //stgs->Chain     = _st_graphic_series_Chain;
        stgs->Update    = _st_graphic_series_Update;
        stgs->Free      = _st_graphic_series_Free;

        stgs->SetPos(stgs, pos);
        stgs->Clear(stgs);
        //stgs->Chain(stgs, stgs);
        stgs->dt = dt;

        return stgs;
    }
    else
    {
        return NULL;
    }

}

/*******************************************************************************
 * func name    :
 * para         :
 *
 * return       : 
 ******************************************************************************/
void        _st_graphic_series_SetPos(st_graphic_series* self, const st_vertex_3d* pos)
{
    self->pos.x = pos->x;
    self->pos.y = pos->y;
    self->pos.z = pos->z;
}

/*******************************************************************************
 * func name    :
 * para         :
 *
 * return       : 
 ******************************************************************************/
st_state    _st_graphic_series_Add(st_graphic_series* self, st_graphic* graphic)
{
    if (self->count < ST_GRAPHIC_SERIES_SIZE)
    {
        self->graphics[self->count] = graphic;
        (self->count)++;
        self->Update(self);
        return ST_OK;
    }
    else
    {
        return ST_ERROR;
    }
}

/*******************************************************************************
 * func name    :
 * para         :
 *
 * return       : 
 ******************************************************************************/
void        _st_graphic_series_Clear(st_graphic_series* self)
{
    self->count     = 0;
    self->current   = 0;
    self->Update(self);
}

//st_state    _st_graphic_series_Chain(st_graphic_series* self, st_graphic_series next);

/*******************************************************************************
 * func name    :
 * para         :
 *
 * return       : 
 ******************************************************************************/
st_graphic* _st_graphic_series_Update(st_graphic_series* self)
{
    if (self->count == 0)
    {
        self->graphic = NULL;
        return NULL;
    }
    else
    {
        st_graphic* stg;
        self->graphic = self->graphics[self->current];
        stg = self->graphic;

        stg->SetPos(stg, &(self->pos));

        self->current++;
        if (self->current >= self->count)
        {
            self->current = 0;
        }
    }
}

/*******************************************************************************
 * func name    :
 * para         :
 *
 * return       : 
 ******************************************************************************/
st_boolean  _st_graphic_series_Free(st_graphic_series* self)
{
    st_uint8 i;
    if (st_Assigned(self))
    {
        for (i=0;i<self->count;i++)
        {
            st_graphic* stg;
            stg = self->graphics[i];
            stg->Free(stg);
        }
        st_Free(self);
    }
}

//st_state _st_graphic_series_Next(st_graphic_series* stgs);
//
//st_state st_graphic_series_Create(st_graphic_series* stgs, 
//                                  const st_graphic*  graphics, 
//                                  const st_uint8     count)
//{
//    st_uint8 i;
//    stgs->count = count;
//    for (i=0;i<stgs->count;i++)
//    {
//        stgs->graphics[i]= (st_graphic*)&graphics[i];
//    }
//    stgs->current = 0;
//    stgs->cur_graph = stgs->graphics[stgs->current];
//
//    return ST_OK;
//}
//
//st_state _st_graphic_series_Next(st_graphic_series* stgs)
//{
//    if (stgs->count == 0)
//    {
//        return ST_ERROR;
//    }
//    else
//    {
//        stgs->current++;
//        if (stgs->current >= stgs->count)
//        {
//            stgs->current=0;
//        }
//        stgs->cur_graph = stgs->graphics[stgs->current];
//        return ST_OK;
//    }
//}
//
//st_graphic* st_graphic_series_Roll(st_graphic_series* stgs)
//{
//    st_graphic* ptr;
//
//    if (stgs->count == 0)
//    {
//        return NULL;
//    }
//    else
//    {
//        ptr = stgs->cur_graph;
//        memcpy(&ptr->pos, &stgs->pos, sizeof(st_vertex_3d));
//
//        _st_graphic_series_Next(stgs);
//
//        return ptr;
//    }
//}
//
//st_state st_graphic_series_SetPosition(st_graphic_series* stgs, const st_vertex_3d* pos)
//{
//    memcpy(&stgs->pos, pos, sizeof(st_vertex_3d));
//}
/*******************************************************************************
 *          多个图片(动画)相关 END}}}1
 ******************************************************************************/


/*******************************************************************************
 *          废弃代码段
 *          {{{1
 ******************************************************************************/
/*******************************************************************************
 *          st_graphic_manager
 * keep all st_graphic's (also st_graphic_series) instance 
 * load/release graphic resources
 * auto-release on termination
 * query resources consumption
 ******************************************************************************/
//#define ST_GRAPHIC_MANAGER_CAPACITY     1024
//st_graphic_manager
//{
//    st_graphic      graphics[ST_GRAPHIC_MANAGER_CAPACITY];



//st_state st_graphic_SetOnDrawingProc(st_graphic* stg, st_callback proc)
//{
//    if (proc != NULL)
//    {
//        stg->on_drawing = proc;
//        return ST_OK;
//    }
//    else
//    {
//        return ST_ERROR;
//    }
//
//}

//st_state st_graphic_SetOwner(st_graphic* stg, const void* owner)
//{
//    stg->owner = owner;
//    return ST_OK;
//}
//
//st_state st_graphic_GetOwner(const st_graphic* stg, void** owner)
//{
//    *owner = stg->owner;
//    return ST_OK;
//}

//st_state st_graphic_SetPosition(st_graphic* stg, const st_vertex_3d* pos)
//{
//    if (pos != NULL)
//    {
//        memcpy(&stg->pos, pos, sizeof(st_vertex_3d));
//        return ST_OK;
//    }
//    else
//    {
//        return ST_ERROR;
//    }
//}
//
//st_state st_graphic_GetPosition(const st_graphic* stg, st_vertex_3d* pos)
//{
//    if (pos != NULL)
//    {
//        memcpy(pos, &stg->pos, sizeof(st_vertex_3d));
//        return ST_OK;
//    }
//    else
//    {
//        return ST_ERROR;
//    }
//}
//
//st_state st_graphic_SetOffset(st_graphic* stg, const st_vertex_3d* offset)
//{
//    if (offset != NULL)
//    {
//        memcpy(&stg->offset, offset, sizeof(st_vertex_3d));
//        return ST_OK;
//    }
//    else
//    {
//        return ST_ERROR;
//    }
//}
//
//st_state st_graphic_GetOffset(const st_graphic* stg, st_vertex_3d* offset)
//{
//    if (offset != NULL)
//    {
//        memcpy(offset, &stg->offset, sizeof(st_vertex_3d));
//        return ST_OK;
//    }
//    else
//    {
//        return ST_ERROR;
//    }
//}
//
//st_state st_graphic_Create(st_graphic* stg, 
//                            const void* owner,
//                            const st_uint8* filename,
//                            const st_callback on_drawing,
//                            const st_vertex_3d* pos,
//                            const st_vertex_3d* offset)
//{
//    st_graphic_CreateNull( stg, owner, on_drawing, pos, offset);
//    if (st_graphic_LoadImageFromBitmap(stg, filename) == ST_ERROR)
//    {
//        return ST_ERROR;
//    }
//    return ST_OK;
//}
/*******************************************************************************
 *          }}}1
 *          废弃代码段 END
 ******************************************************************************/

/*******************************************************************************
 *          NOTE
 *          {{{1
 * 1. About pos and offset
 * pos and offset of a graph are used to calculate the exact coordination of the 
 * graph, which describes where the graph should be blit.
 * see illustration below:
 * base=(0,0)
 *       \
 *        +----------------------------------------+
 *        |                                        |
 *        |                                        |
 *        |                                        |
 *        |                                        |
 *        |   (pos+offset)      _graph             |
 *        |              \     /                   |
 *        |               +---+                    |
 *        |               |   |                    |
 *        |               |   |                    |
 *        |               +---+                    |
 *        |                                        |
 *        |                                        |
 *        |                                        |
 *        |                                        |
 *        |                                        |
 *        |                                        |
 *        |                (main)Surface           |
 *        |                                        |
 *        |                                        |
 *        |                                        |
 *        |                                        |
 *        |                                        |
 *        +----------------------------------------+
 * the base is the left-top corner of a surface. we use pos+offset to get the 
 * left-top corner of a graph to the base, so as to draw it on the surface.
 *
 *          }}}1
 *          NOTE END
 ******************************************************************************/


